package fms.controller;

import fms.dao.CptInfoService;
import fms.dao.Grib2InfoService;
import fms.dao.PlanCptWdService;
import fms.model.PlanCptWd;
import fms.util.*;
import fms.webservice.FMSData;
import fms.xml.WindRequestXML;
import org.apache.log4j.Logger;

import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.collections.ListUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;


/**
 * Created with IntelliJ IDEA.
 * Desc: 本线程用于FlightOperator与JETPLAN风温数据的对比统计，
 *       即，将每一份已入库的飞行计划中的每个高度层中的各个航路点数据，与FlightOperator利用Grib2计算出来的数据进行对比，
 *       把对比结果生成一个EXCEL文档
 */
public class ContrastStatisticsController {

    private Logger logger = Logger.getLogger(ContrastStatisticsController.class);

    // 待处理的飞行计划ID集合
    private List flyPlanIdList = null;
    // 当前飞行计划对应的航路点名称列表
    private ArrayList cptnameList = new ArrayList();
    // 当前飞行计划对应的高度层名称列表
    private ArrayList altList = new ArrayList();

    // 用于进行Grib2风温计算的对象
    public final static String Grib2PointTableCPT = "CPTINFO";
    private Grib2Controller grib2Controller = new Grib2Controller(Grib2PointTableCPT);
    // 指定有效期内、高度层的Grib2数据总量
    private final int perPeriodAltGrib2Num = 125280;

    // 提供数据库交互服务的对象
    private CptInfoService cptInfoService = null;
    private PlanCptWdService planCptWdService = null;
    private Grib2InfoService grib2InfoService = null;

    // 该文件以“飞行计划ID + 空格”的方式存储已处理的飞行计划ID集合
    private final String FlyPlanIdFilePath = "ProcessedFlyPlanID.data";
    // 代表ProcessedFlyPlanID.data是否存在
    private File flyPlanIdFile = null;

    private String excelOuputPath;

    /**
     *  算法描述：
     *      1、获取已经处理过的航班计划ID集合
     *      2、查询数据库中存在的航班计划ID集合
     *      3、求出需要对比分析的飞行计划ID集合
     */
    public ContrastStatisticsController( String excelOuputPath ) throws Exception {
        this.excelOuputPath = excelOuputPath;
        FileReader flyPlanIdFileReader = null;
        BufferedReader bufReader = null;

        try {
            logger.debug("开始构造风温数据对比统计对象！");
            cptInfoService = ConfUtil.getSingletonApplicationContextDao().getBean("cptInfoService", CptInfoService.class);
            planCptWdService = ConfUtil.getSingletonApplicationContextDao().getBean("planCptWdService", PlanCptWdService.class);
            grib2InfoService = ConfUtil.getSingletonApplicationContextDao().getBean("grib2InfoService", Grib2InfoService.class);

            if (null == cptInfoService || null == planCptWdService) {
                logger.error("ContrastStatisticsController()：JPA对象cptInfoService、planCptWdService获取失败");
                throw new Exception("");
            }

            // 判断指定目录中是否存在文件
            flyPlanIdFile = new File(FlyPlanIdFilePath);
            if (!flyPlanIdFile.exists()) {
                flyPlanIdFile.createNewFile();
            }
            flyPlanIdFileReader = new FileReader(flyPlanIdFile);
            bufReader = new BufferedReader(flyPlanIdFileReader);
            String flyPlanIdContent = bufReader.readLine();

            // 已处理的飞行计划ID集合
            List processedFlyPlanIdList = null;
            if ( !ObjectUtil.isBlank(flyPlanIdContent) ) {
                String[] arrFlyPlanId = flyPlanIdContent.trim().split(" +");
                processedFlyPlanIdList = Arrays.asList(arrFlyPlanId);
            }
            // 数据库中存在的飞行计划ID集合（仅查询两天以内的飞行计划即可，因为，在风温计算时，仅会或取两天以内的飞行计划的航路点）
            List dbFlyPlanIdList = cptInfoService.getPlanIdList();

            if (null == dbFlyPlanIdList) {
                logger.debug("待处理的飞行计划ID集合为空！");
                flyPlanIdList = null;
            } else if (null == processedFlyPlanIdList) {
                flyPlanIdList = dbFlyPlanIdList;
            } else {
                flyPlanIdList = ListUtils.subtract(dbFlyPlanIdList, processedFlyPlanIdList);
            }
            logger.debug("完成构造风温数据对比统计对象！");
        } catch (Exception e) {
            throw new Exception("ContrastStatisticsController()：加载dao对象失败 or 文件ProcessedFlyPlanID.data创建失败！");
        } finally {
            if (null != flyPlanIdFileReader) {
                flyPlanIdFileReader.close();
            }
            if (null != bufReader) {
                bufReader.close();
            }
        }

    }


    /**
     * @see : 从一个航班计划的所有风温数据中获取去重后的航路点名称集合
     * @return : 一个航班计划对应的去重后的航路点名称集合
     */
    private void GetDistinctCptnameDatas(List planCptWdList) {
        for (int i = 0; i < planCptWdList.size(); i++) {
            PlanCptWd planCptWd = (PlanCptWd) planCptWdList.get(i);
            // 避免重复
            int cptnameExist = cptnameList.indexOf( planCptWd.getCptName() );
            if (cptnameExist >= 0) {
                continue;
            } else {
                cptnameList.add( planCptWd.getCptName() );
            }
        }
    }

    /**
     * @see : 从一个航班计划的所有风温数据中获取去重后的高度层集合
     * @return : 一个航班计划对应的去重后的高度层名称集合
     */
    private void GetDistinctAltDatas(List planCptWdList) {
        for (int i = 0; i < planCptWdList.size(); i++) {
            PlanCptWd planCptWd = (PlanCptWd) planCptWdList.get(i);
            // 避免重复
            int altExist = altList.indexOf(  String.valueOf((int) planCptWd.getAlt()) );
            if (altExist >= 0) {
                continue;
            } else {
                altList.add( String.valueOf((int)planCptWd.getAlt()) );
            }
        }
    }

    // 当前飞行计划计算对应Grib2风温数据时，所需参数（仅一个高度层）
    private WindRequestXML getWindRequestXML(List planCptWdList, String alt, String cptnames) {
        WindRequestXML windRequestXML = new WindRequestXML();
        PlanCptWd planCptWd = (PlanCptWd) planCptWdList.get(0);
        windRequestXML.setAircraftid( planCptWd.getAircraftId() );
        // 填充两个任意字符，因为风温请求时，构造的WindRequestXML对象，使用的是从Acars下行报文中截取的带有航空公司二字码的航班号
        windRequestXML.setFlightID("XX" + planCptWd.getFlightId() );
        windRequestXML.setAlt( alt );
        windRequestXML.setCptname( cptnames );

        return windRequestXML;
    }

    // 根据当前UTC的时间来获取Grib2数据起止时间
    // flAlt : 为飞行计划中使用飞行高度层
    private String getValidGrib2Period(int flAlt) {
        // 飞行高度层转气象高度层
        BigDecimal hpaAlt = GribUtil.fl2hpa(new BigDecimal(flAlt));
        // 获取指定高度层所对应的上下两个标准高度层
        List<Integer> altList = GribUtil.getTwoStandardAltitudes(hpaAlt.doubleValue());


        String hour = DateUtil.getCurUTCtime("HH");
        List timelist = null;
        long curPeriodAltsGrib2Num = 0;

        timelist = GribUtil.getTimes(Integer.valueOf(hour),6);
        curPeriodAltsGrib2Num = grib2InfoService.countByTimeAlt((String)timelist.get(0), (String)timelist.get(1),
                altList.get(0), altList.get(1));
        if (perPeriodAltGrib2Num * 2 == curPeriodAltsGrib2Num) {
            return (String)timelist.get(0) + " - " + (String)timelist.get(1);
        }

        timelist = GribUtil.getTimes(Integer.valueOf(hour),12);
        curPeriodAltsGrib2Num = grib2InfoService.countByTimeAlt((String)timelist.get(0), (String)timelist.get(1),
                altList.get(0), altList.get(1));
        if (perPeriodAltGrib2Num * 2 == curPeriodAltsGrib2Num) {
            return (String)timelist.get(0) + " - " + (String)timelist.get(1);
        }

        timelist = GribUtil.getTimes(Integer.valueOf(hour),18);
        curPeriodAltsGrib2Num = grib2InfoService.countByTimeAlt((String)timelist.get(0), (String)timelist.get(1),
                altList.get(0), altList.get(1));
        if (perPeriodAltGrib2Num * 2 == curPeriodAltsGrib2Num) {
            return (String)timelist.get(0) + " - " + (String)timelist.get(1);
        }

         return "";
    }

    // 按高度层、航路点名查询其对应的飞行计划中的风温数据
    private PlanCptWd getPlanWindInfo(List flyPlanWindInfoList, int alt, String cptname) {
        PlanCptWd planCptWd = null;
        for (int i = 0; i < flyPlanWindInfoList.size(); i++) {
            planCptWd = (PlanCptWd) flyPlanWindInfoList.get(i);

            if ( planCptWd.getCptName().equals(cptname)
                    && (alt == planCptWd.getAlt()) ) {
                break;
            }
        }
        return planCptWd;
    }

    /**
     * @desc : 向EXECEL中插入三行“对比标头信息”
     * @param sheet : 向哪个sheet中插入
     * @param reqFlAlt : 请求高度层
     * @param grib2ValidPeriod : grib2数据有效时间
     * @param execelRowCount : sheet的行记录器
     * @return : 返回当前sheet的行记录器
     */
    private int insertAltCompareTitle(HSSFSheet sheet, int reqFlAlt, String grib2ValidPeriod, int execelRowCount) {
        HSSFRow newRow = sheet.createRow(execelRowCount++);
        HSSFCell cell = newRow.createCell((short) 0);
        if (ObjectUtil.isBlank(grib2ValidPeriod)) {
            cell.setCellValue("高度：" + reqFlAlt + "  Grib2数据有效时间：未找到可用的Grib2数据");

            logger.debug("高度：" + reqFlAlt + "  Grib2数据有效时间：未找到可用的Grib2数据");
            return execelRowCount;
        }
        cell.setCellValue("高度层：" + reqFlAlt + "  Grib2数据有效时间：" + grib2ValidPeriod);

        logger.debug("高度层：" + reqFlAlt + "  Grib2数据有效时间：" + grib2ValidPeriod);
        newRow = sheet.createRow(execelRowCount++);
        cell = newRow.createCell((short) 0);
        cell.setCellValue("风向数据对比");
        cell = newRow.createCell((short) 5);
        cell.setCellValue("风速数据对比");
        cell = newRow.createCell((short) 10);
        cell.setCellValue("风温数据对比");

        newRow = sheet.createRow(execelRowCount++);
        cell = newRow.createCell((short) 0);
        cell.setCellValue("航路点");
        cell = newRow.createCell((short) 1);
        cell.setCellValue("飞行计划");
        cell = newRow.createCell((short) 2);
        cell.setCellValue("SKYFMS");
        cell = newRow.createCell((short) 3);
        cell.setCellValue("差值");

        cell = newRow.createCell((short) 5);
        cell.setCellValue("航路点");
        cell = newRow.createCell((short) 6);
        cell.setCellValue("飞行计划");
        cell = newRow.createCell((short) 7);
        cell.setCellValue("SKYFMS");
        cell = newRow.createCell((short) 8);
        cell.setCellValue("差值");

        cell = newRow.createCell((short) 10);
        cell.setCellValue("航路点");
        cell = newRow.createCell((short) 11);
        cell.setCellValue("飞行计划");
        cell = newRow.createCell((short) 12);
        cell.setCellValue("SKYFMS");
        cell = newRow.createCell((short) 13);
        cell.setCellValue("差值");

        return execelRowCount;
    }

    /**
     * @desc : 向EXECEL中插入三行“对比标头信息”
     * @param sheet : 向哪个sheet中插入
     * @param windInfoByGrib2 : 当前高度层、航路点名对应的Grib2风温数据
     * @param windInfoByFlyplan : 当前高度层、航路点名对应的飞行计划风温数据
     * @param execelRowCount : sheet的行记录器
     * @return : 返回当前sheet的行记录器
     */
    private int insertAltCompareData(HSSFSheet sheet, FMSData windInfoByGrib2, PlanCptWd windInfoByFlyplan, int execelRowCount) {
        HSSFRow newRow = sheet.createRow(execelRowCount++);
        HSSFCell cell = null;
        // 风向对比结果
        cell = newRow.createCell((short) 0);
        cell.setCellValue(windInfoByGrib2.getCptname());
        cell = newRow.createCell((short) 1);
        // 飞行计划中风向会省略个位数，所以，要乘10补位
        cell.setCellValue((int)windInfoByFlyplan.getWindDir() * 10);
        cell = newRow.createCell((short) 2);
        cell.setCellValue(windInfoByGrib2.getWinddir());
        cell = newRow.createCell((short) 3);
        cell.setCellValue((int)windInfoByFlyplan.getWindDir() * 10 - Integer.valueOf(windInfoByGrib2.getWinddir()));
        // 风速对比结果
        cell = newRow.createCell((short) 5);
        cell.setCellValue(windInfoByGrib2.getCptname());
        cell = newRow.createCell((short) 6);
        cell.setCellValue((int)windInfoByFlyplan.getWindVel());
        cell = newRow.createCell((short) 7);
        cell.setCellValue(windInfoByGrib2.getWindvel());
        cell = newRow.createCell((short) 8);
        cell.setCellValue((int)windInfoByFlyplan.getWindVel() - Integer.valueOf(windInfoByGrib2.getWindvel()));
        // 风温对比结果
        cell = newRow.createCell((short) 10);
        cell.setCellValue(windInfoByGrib2.getCptname());
        cell = newRow.createCell((short) 11);
        cell.setCellValue((int)windInfoByFlyplan.getWindTep());
        cell = newRow.createCell((short) 12);
        cell.setCellValue(windInfoByGrib2.getWindtep());
        cell = newRow.createCell((short) 13);
        cell.setCellValue((int)windInfoByFlyplan.getWindTep() - Integer.valueOf(windInfoByGrib2.getWindtep()));

        return execelRowCount;
    }

    public boolean startContrastStatistics() {
        if (null == flyPlanIdList) {
            logger.info("startContrastStatistics : 不存在需要对比统计风温数据的飞行计划!");
            return true;
        }

        FileWriter flyPlanIdFileWriter = null;
        String flyPlanExecelName = "";
        try {
            logger.debug("开始风温数据对比统计！");
            //打开一个写文件器，构造函数中的第二个参数true表示以追加形式写文件
            flyPlanIdFileWriter = new FileWriter(flyPlanIdFile, true);

            logger.debug("尚未处理的飞行计划份数：" + flyPlanIdList.size());
            // 步骤一：获取指定飞行计划ID所对应的所有高度层航路点风温数据
            for (int iFlyPlanID = 0; iFlyPlanID < flyPlanIdList.size(); iFlyPlanID++) {
                if ( ObjectUtil.isBlank((String)flyPlanIdList.get(iFlyPlanID)) ) {
                    logger.debug("发现一份飞行计划ID为空！");
                    continue;
                }
                // excel行计数器
                int execelRowCount = 0;

                logger.debug("开始查询飞行计划风温数据，飞行计划ID为：" + (String)flyPlanIdList.get(iFlyPlanID));
                List planCptWdList = planCptWdService.findByPlanId((String) flyPlanIdList.get(iFlyPlanID));
                logger.debug("完成查询飞行计划风温数据，飞行计划ID为：" + (String)flyPlanIdList.get(iFlyPlanID));
                PlanCptWd planCptWd = (PlanCptWd) planCptWdList.get(0);
                // 步骤二：创建一个EXCEL，以“航班计划日期_航班号_机尾号_文档生成UTC时间”命名
                flyPlanExecelName = excelOuputPath + planCptWd.getPlanDate().toString().split(" ")[0]
                                    + "_" + planCptWd.getAirlines() + planCptWd.getFlightId()
                                    + "_" + planCptWd.getAircraftId()
                                    + "_" + DateUtil.getCurUTCtime(SysConstants.FORMAT_DATETIME_PURE) +".xls";
                logger.debug("开始内存创建风温数据对比文件:" + flyPlanExecelName);
                // EXECEL生成之第1步：创建一个webbook，对应一个Excel文件
                HSSFWorkbook wb = new HSSFWorkbook();
                logger.debug("结束内存创建风温数据对比文件:" + flyPlanExecelName);
                // EXECEL生成之第2步：在webbook中添加一个sheet,对应Excel文件中的sheet
                HSSFSheet sheet = wb.createSheet("飞行计划与SKYFMS风温数据对比表");

                // 获取航路点集合
                cptnameList.clear();
                GetDistinctCptnameDatas(planCptWdList);

                // 获取高度层集合
                altList.clear();
                GetDistinctAltDatas(planCptWdList);

                String strCptNames = StringUtil.list2String(cptnameList, ".");

                logger.debug("飞行计划ID：" + (String)flyPlanIdList.get(iFlyPlanID) + "； 高度层数量：" + altList.size());
                // 步骤三：遍历该飞行计划包含的所有高度层，按高度层写EXCEL中写入各个航路点的风温数据的对比情况
                for (int jAlt = 0; jAlt < altList.size(); jAlt++) {
                    // 飞行计划中的高度层值是风温请求的100倍
                    int reqFlAlt = Integer.valueOf((String)altList.get(jAlt)) / 100;

                    // 步骤3-1：获取当前最恰当的Grib2数据有效时间区间
                    String grib2ValidPeriod = getValidGrib2Period( Integer.valueOf((String)altList.get(jAlt)) );
                    logger.debug("grib2ValidPeriod：" + grib2ValidPeriod);
                    // 步骤3-2：向Excel中写入当前高度层数据
                    // EXECEL生成之第3步，在sheet中添加1行,注意老版本poi对Excel的行数列数有限制short
                    execelRowCount = insertAltCompareTitle( sheet, reqFlAlt, grib2ValidPeriod, execelRowCount);

                    logger.debug("当前处理的飞行计划高度层为：" + altList.get(jAlt));

                    // 没有可用的Grib2数据，就不用进行风温计算了
                    if (!ObjectUtil.isBlank(grib2ValidPeriod)) {
                        WindRequestXML windResponseXML = getWindRequestXML(planCptWdList, String.valueOf(reqFlAlt), strCptNames);
                        // 获得该高度层中Grib2计算出来的各个航路点风温数据
                        List listWindInfoByGrib2 = grib2Controller.gerGrid2Response(windResponseXML);
                        if (null != listWindInfoByGrib2) {
                            // 步骤3-3：遍历Grib2计算出的风温数据，按高度层、航路点名查询其对应的飞行计划中的风温数据
                            for (int k = 0; k < listWindInfoByGrib2.size(); k++) {
                                FMSData windInfoByGrib2 = (FMSData)listWindInfoByGrib2.get(k);
                                PlanCptWd windInfoByFlyplan = getPlanWindInfo(planCptWdList, Integer.valueOf((String) altList.get(jAlt)),
                                        windInfoByGrib2.getCptname());
                                logger.debug("航路点名：" + windInfoByGrib2.getCptname()
                                        + "（" + (int)windInfoByFlyplan.getWindDir() * 10 + "," + (int)windInfoByFlyplan.getWindVel() + ","+ (int)windInfoByFlyplan.getWindTep() + ")"
                                        + "(" + windInfoByGrib2.getWinddir()+ "," + windInfoByGrib2.getWindvel() + "," + windInfoByGrib2.getWindtep() + ")" );

                                // 步骤3-4：向EXCEL中依次写入本高度层上的，风向、风速、风温对比数据
                                execelRowCount = insertAltCompareData(sheet,windInfoByGrib2,windInfoByFlyplan,execelRowCount);
                            }
                        }
                    }
                } //  for (int jAlt = 0; jAlt < altList.size(); jAlt++) {
                // 向ProcessedFlyPlanID.data文件中，写入已经处理的飞行计划ID
                flyPlanIdFileWriter.write((String) flyPlanIdList.get(iFlyPlanID) + " ");

                FileOutputStream fout = new FileOutputStream(flyPlanExecelName);
                wb.write(fout);
                fout.close();
            } // for (int iFlyPlanID = 0; iFlyPlanID < flyPlanIdList.size(); iFlyPlanID++) {
        } catch (Exception e) {
            logger.error(e.toString());
            return false;
        } finally {
            try {
                if (null != flyPlanIdFileWriter) {
                    flyPlanIdFileWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

}
